////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2010 Gabriel Montagné Láscaris-Comneno and Alberto
//  Brealey-Guzmán.
//
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

package com.rojored.core
{

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;
import flash.utils.Proxy;
import flash.utils.flash_proxy;
import mx.events.PropertyChangeEvent;

use namespace flash_proxy;

//--------------------------------------
//   Other metadata
//--------------------------------------

[Bindable]

/**
 *  Simple bindable hash implementation.
 *
 *  <p>Keys and value indices will match but if there's a change or deletion
 *  of any of the properties the property enumeration order will be disrupted
 *  order will be disrupted.</p>
 */
public dynamic class Hash extends Proxy implements IEventDispatcher
{

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor
     *
     *  @param weakKeys Instructs the Hash object (via the underlying
     *  Dictionary) to use "weak" references on object keys.  If the only
     *  reference to an object is in the specified Dictionary object, the key
     *  is eligible for garbage collection and is removed from the table when
     *  the object is collected.
     *
     *  @see flash.core.Dictionary
     */
    public function Hash(weakKeys:Boolean = false)
    {
        super();
        propertyMap = new Dictionary(weakKeys);
    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private var propertyMap:Dictionary;

    /**
     *  @private
     */
    private var enumarationList:Array;

    /**
     *  @private
     */
    private var eventDispatcher:EventDispatcher = new EventDispatcher();

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //--------------------------------------
    //   keys
    //--------------------------------------

    /**
     *  @private
     */
    private var _keys:Array;

    /**
     *  Keys for all the values.
     */
    public function get keys():Array
    {
        if (!_keys)
            updateKeysAndValuesList();

        return _keys;
    }

    //--------------------------------------
    //   values
    //--------------------------------------

    /**
     *  @private
     */
    private var _values:Array;

    /**
     *  Values for all the keys.
     */
    public function get values():Array
    {
        if (!_values)
            updateKeysAndValuesList();

        return _values;
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods: Proxy
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override flash_proxy function getProperty(name:*):*
    {
        return propertyMap[name];
    }

    /**
     *  @private
     */
    override flash_proxy function callProperty(name:*, ... rest):*
    {
        return propertyMap[name].apply(this, rest);
    }

    /**
     *  @private
     */
    override flash_proxy function setProperty(name:*, value:*):void
    {
        var oldValue:* = propertyMap[name];
        if (oldValue === value)
            return;

        voidKeysAndValues();

        propertyMap[name] = value;
        dispatchEvent(
            PropertyChangeEvent.createUpdateEvent(
                this,
                name,
                oldValue,
                propertyMap[name]
            )
        );
    }

    /**
     *   @private
     */
    override flash_proxy function hasProperty(name:*):Boolean
    {
        return name in propertyMap;
    }

    /**
     *  @private
     */
    override flash_proxy function nextName(index:int):String
    {
        return enumarationList[index - 1];
    }

    /**
     *  @private
     */
    override flash_proxy function nextNameIndex(index:int):int
    {
        if (index == 0)
            enumarationList = keys.slice();

        if (index < enumarationList.length)
            return index + 1;
        else
            return 0;
    }

    /**
     *  @private
     */
    override flash_proxy function nextValue(index:int):*
    {
        return propertyMap[enumarationList[index -1]];
    }

    /**
     *  @private
     */
    override flash_proxy function deleteProperty(name:*):Boolean
    {
        voidKeysAndValues();
        var oldValue:* = propertyMap[name];
        var result:Boolean =  delete propertyMap[name];
        dispatchEvent(
            PropertyChangeEvent.createUpdateEvent(
                this,
                name,
                oldValue,
                undefined
            )
        );
        return result;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods: IEventDispatcher
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    public function addEventListener(type:String, listener:Function,
                                     useCapture:Boolean = false,
                                     priority:int = 0,
                                     useWeakReference:Boolean = false):void
    {
        eventDispatcher.addEventListener(type, listener, useCapture, priority,
            useWeakReference);
    }

    /**
     *  @private
     */
    public function dispatchEvent(event:Event):Boolean
    {
        return eventDispatcher.dispatchEvent(event);
    }

    /**
     *  @private
     */
    public function hasEventListener(type:String):Boolean
    {
        return eventDispatcher.hasEventListener(type);
    }

    /**
     *  @private
     */
    public function removeEventListener(type:String, listener:Function,
                                        useCapture:Boolean = false):void
    {
        eventDispatcher.removeEventListener(type, listener, useCapture);
    }

    /**
     *  @private
     */
    public function willTrigger(type:String):Boolean
    {
        return eventDispatcher.willTrigger(type);
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private function updateKeysAndValuesList():void
    {
        _keys = [];
        _values = [];
        for (var key:* in propertyMap)
        {
            _keys.push(key);
            _values.push(propertyMap[key]);
        }
    }

    /**
     *  @private
     */
    private function voidKeysAndValues():void
    {
        _keys = null;
        _values = null;
    }
}
}
